package icu.zhhll.mybatis.interceptors;

import icu.zhhll.mybatis.page.Page;
import icu.zhhll.mybatis.page.PageParam;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Field;
import java.util.*;

/**
 * mybatis分页插件
 * 在mybatis配置文件中配置
 * <p>
 * <code>
 * <plugins>
 * <plugin interceptor="icu.zhhll.mybatis.interceptors.OffsetLimitInterceptor"/>
 * </plugins>
 *
 * </code>
 * </p>
 * 然后对于需要分页查询的语句增加PageParam参数,会按照分页参数来自动进行分页
 * <p>
 * <code>
 *
 * @author zh
 * @Select("select * from student where class_id = #{classId}")
 * @Results( {
 * @Result(id = true,column = "id",property = "id"),
 * @Result(column = "name",property = "name")
 * }
 * )
 * Page<Student> getByClassIdPage(@Param(value = "classId") int classId, @Param(value = "pageParam") PageParam pageParam);
 * </code>
 * </p>
 * @date 2023/6/12 17:54
 */
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
        RowBounds.class, ResultHandler.class})})
public class OffsetLimitInterceptor implements Interceptor {

    public Object intercept(Invocation invocation) throws Throwable {
        Object[] queryArgs = invocation.getArgs();
        RowBounds rowBounds = (RowBounds) queryArgs[2];
        PageParam pageParam = new PageParam(rowBounds);
        // 没有使用分页，直接执行返回
        if (pageParam.getOffset() == 0 && pageParam.getLimit() == Integer.MAX_VALUE) {
            return invocation.proceed();
        }
        Executor executor = (Executor) invocation.getTarget();
        MappedStatement ms = (MappedStatement) queryArgs[0];
        // 获取参数
        Object parameter = queryArgs[1];
        // 拿到预编译sql
        BoundSql boundSql = ms.getBoundSql(parameter);
        // 若是使用Criteria对象时的sql，那么在additionalParameters中便有值
        Field additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");
        additionalParametersField.setAccessible(true);
        Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql);
        // 使用了该配套page参数
        if (rowBounds instanceof PageParam) {
            // 查询总条数
            MappedStatement countMs = countMappedStatement(ms);
            // 创建 count 查询的缓存 key
            CacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);
            String countSql = "select count(*) from (" + boundSql.getSql() + ") temp";
            BoundSql countBoundSql = new BoundSql(ms.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
            Set<String> keySet = additionalParameters.keySet();
            for (String key : keySet) {
                countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
            }
            // 查询
            List<Object> countQueryResult = executor.query(countMs, parameter, RowBounds.DEFAULT,
                    (ResultHandler) queryArgs[3], countKey, countBoundSql);
            int count;
            if (countQueryResult == null || countQueryResult.size() == 0) {
                count = 0;
            } else {
                count = (int) countQueryResult.get(0);
            }

            pageParam.setTotalCount(count);
        }
        // 生成缓存key
        CacheKey pageKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
        pageKey.update("RowBounds");
        // 分页sql
        String pageSql = boundSql.getSql() + " limit " + rowBounds.getOffset() + "," + rowBounds.getLimit();
        BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);
        Set<String> keySet = additionalParameters.keySet();
        for (String key : keySet) {
            pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
        }
        List result = executor.query(ms, parameter, RowBounds.DEFAULT, (ResultHandler) queryArgs[3], pageKey, pageBoundSql);

        return new Page(result, pageParam);
    }

    /**
     * 查询总条数的MappedStatement
     *
     * @param ms
     * @return
     */
    private MappedStatement countMappedStatement(MappedStatement ms) {
        MappedStatement.Builder builder = new MappedStatement.Builder(
                ms.getConfiguration(), ms.getId() + "_count", ms.getSqlSource(), ms.getSqlCommandType()
        );
        ResultMap resultMap = new ResultMap.Builder(ms.getConfiguration(), ms.getId(), Integer.class, new ArrayList<>(0)).build();
        builder.resource(ms.getResource())
                .fetchSize(ms.getFetchSize())
                .statementType(ms.getStatementType())
                .timeout(ms.getTimeout())
                .parameterMap(ms.getParameterMap())
                .resultSetType(ms.getResultSetType())
                .cache(ms.getCache())
                .flushCacheRequired(ms.isFlushCacheRequired())
                .useCache(ms.isUseCache())
                .resultMaps(Collections.singletonList(resultMap));
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        return builder.build();
    }

    public Object plugin(final Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(final Properties properties) {

    }
}
